﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace FirstFloor.P2P.Reversi
{
    /// <summary>
    /// Represents the reversi board.
    /// </summary>
    public partial class Board : UserControl
    {
        /// <summary>
        /// The board length
        /// </summary>
        public const int BoardLength = 8;
        private const int PieceSize = 43;

        /// <summary>
        /// Occurs when a piece is selected.
        /// </summary>
        public event EventHandler<LocationEventArgs> PieceSelected;

        private Piece[,] board;

        /// <summary>
        /// Initializes a new instance of the <see cref="Board"/> class.
        /// </summary>
        public Board()
        {
            InitializeComponent();

            this.board = new Piece[BoardLength, BoardLength];

            for (int x = 0; x < BoardLength; x++) {
                for (int y = 0; y < BoardLength; y++) {
                    Piece piece = new Piece(x, y);
                    this.board[x, y] = piece;

                    Image image = new Image();
                    image.Source = new BitmapImage(piece.GetImageUrl());
                    Canvas.SetLeft(image, x * PieceSize);
                    Canvas.SetTop(image, y * PieceSize);

                    this.root.Children.Add(image);
                }
            }
        }

        /// <summary>
        /// When implemented in a derived class, provides the behavior for the "Arrange" pass of Silverlight layout.
        /// </summary>
        /// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
        /// <returns>The actual size used.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            double width = Math.Min(BoardLength * PieceSize, finalSize.Width);
            double height = Math.Min(BoardLength * PieceSize, finalSize.Height);

            double result = Math.Min(width, height);

            this.scale.ScaleX = result / BoardLength / PieceSize;
            this.scale.ScaleY = result / BoardLength / PieceSize;

            return new Size(result, result);
        }

        private Piece GetBoardPiece(int x, int y)
        {
            if (x < 0 || x > BoardLength) {
                throw new ArgumentOutOfRangeException("x");
            }
            if (y < 0 || y > BoardLength) {
                throw new ArgumentOutOfRangeException("y");
            }

            return this.board[x, y];
        }

        private void UpdateBoardPiece(Piece piece, PieceColor color, bool marked, bool selectable)
        {
            bool updated = piece.Color != color || piece.Marked != marked || piece.Selectable != selectable;

            if (updated) {
                piece.Color = color;
                piece.Marked = marked;
                piece.Selectable = selectable;

                Image image = (Image)this.root.Children[piece.X * 8 + piece.Y];
                image.Source = new BitmapImage(piece.GetImageUrl());
                image.Cursor = selectable ? Cursors.Hand : null;
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="FirstFloor.Reversi.PieceColor"/> at specified position.
        /// </summary>
        /// <value></value>
        public PieceColor this[int x, int y]
        {
            get
            {
                Piece piece = GetBoardPiece(x, y);
                return piece.Color;
            }
            set
            {
                Piece piece = GetBoardPiece(x, y);
                UpdateBoardPiece(piece, value, piece.Marked, piece.Selectable);
            }
        }

        /// <summary>
        /// Gets the number of pieces having specified color.
        /// </summary>
        /// <param name="color">The color.</param>
        /// <returns></returns>
        public int GetPieceCount(PieceColor color)
        {
            int count = 0;
            for (int i = 0; i < BoardLength; i++) {
                for (int j = 0; j < BoardLength; j++) {
                    if (this.board[i, j].Color == color) {
                        count++;
                    }
                }
            }
            return count;
        }

        /// <summary>
        /// Clears the board.
        /// </summary>
        public void Clear()
        {
            for (int x = 0; x < BoardLength; x++) {
                for (int y = 0; y < BoardLength; y++) {
                    UpdateBoardPiece(GetBoardPiece(x, y), PieceColor.None, false, false);
                }
            }
        }

        /// <summary>
        /// Clears the marks.
        /// </summary>
        public void ClearMarks()
        {
            for (int x = 0; x < BoardLength; x++) {
                for (int y = 0; y < BoardLength; y++) {
                    Piece piece = GetBoardPiece(x, y);
                    UpdateBoardPiece(piece, piece.Color, false, piece.Selectable);
                }
            }
        }

        /// <summary>
        /// Set all pieces unselectable.
        /// </summary>
        public void ClearSelectables()
        {
            for (int x = 0; x < BoardLength; x++) {
                for (int y = 0; y < BoardLength; y++) {
                    Piece piece = GetBoardPiece(x, y);
                    UpdateBoardPiece(piece, piece.Color, piece.Marked, false);
                }
            }
        }

        /// <summary>
        /// Clears the marks and selectable pieces.
        /// </summary>
        public void ClearMarksAndSelectables()
        {
            for (int x = 0; x < BoardLength; x++) {
                for (int y = 0; y < BoardLength; y++) {
                    Piece piece = GetBoardPiece(x, y);
                    UpdateBoardPiece(piece, piece.Color, false, false);
                }
            }
        }

        /// <summary>
        /// Marks or unmarks the specified piece.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="mark">if set to <c>true</c> mark.</param>
        public void MarkPiece(int x, int y, bool mark)
        {
            Piece piece = GetBoardPiece(x, y);

            UpdateBoardPiece(piece, piece.Color, mark, piece.Selectable);
        }

        /// <summary>
        /// Set the specified piece selectable.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="selectable">if set to <c>true</c> selectable.</param>
        public void SetSelectable(int x, int y, bool selectable)
        {
            Piece piece = GetBoardPiece(x, y);

            UpdateBoardPiece(piece, piece.Color, piece.Marked, selectable);
        }

        private void root_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            FrameworkElement element = e.OriginalSource as FrameworkElement;
            if (element != null) {
                Image image = element.Parent as Image;

                if (image != null) {
                    Location location = Location.GetLocationFromIndex(this.root.Children.IndexOf(image));
                    if (PieceSelected != null) {
                        PieceSelected(this, new LocationEventArgs(location));
                    }
                }
            }
        }
    }
}
